TypeScript 文件模块化
声明文件
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
这里是涉及到的一些关键字
declare var
声明全局变量declare function
声明全局方法declare class
声明全局类declare enum
声明全局枚举类型declare namespace
声明(含有子属性的)全局对象interface 和 type
声明全局类型export
导出变量export namespace
导出(含有子属性的)对象export default
ES6 默认导出export =
commonjs 导出模块export as namespace
UMD 库声明全局变量declare global
扩展全局变量declare module
扩展模块/// <reference />
三斜线指令export declare
其实这个就和直接使用declare
一样,只不过前者需要使用import
导入,后者直接用(使用declare
主要是为了兼容老模块)
在不同的场景下,声明文件的内容和使用方式会有所区别。
库的使用场景主要有以下几种:
- 全局变量:通过
<script>
标签引入第三方库,注入全局变量 - npm 包:通过
import foo from 'foo'
导入,符合 ES6 模块规范 - UMD 库:既可以通过
<script>
标签引入,又可以通过import
导入 - 直接扩展全局变量:通过
<script>
标签引入后,改变一个全局变量的结构 - 在 npm 包或 UMD 库中扩展全局变量:引用 npm 包或 UMD 库后,改变一个全局变量的结构
- 模块插件:通过
<script>
或import
导入后,改变另一个模块的结构
什么是声明文件
通常我们会把声明语句放到一个单独的文件(例如 jQuery.d.ts
)中,这就是声明文件
// src/jQuery.d.ts
declare var jQuery: (selector: string) => any;
声明文件必需以 .d.ts
为后缀
一般来说,ts 会解析项目中所有的 *.ts
文件,当然也包含以 .d.ts
结尾的文件。所以当我们将 jQuery.d.ts
放到项目中时,其他所有 *.ts
文件就都可以获得 jQuery 的类型定义了。
什么是声明语句(declare 关键字)
假如我们想使用第三方库 ,一种常见的方式是在 html 中通过 <script>
标签引入 jQuery,然后就可以使用全局变量 $
或 jQuery 了。
$('#foo');
// or
jQuery('#foo');
但是在 ts 中,编译器并不知道 $
或 jQuery 是什么东西,所以需要使用 declare var
来定义它的类型
declare var jQuery: (selector: string) => any;
jQuery('#foo');
declare var
并没有真的定义一个变量,只是定义了全局变量 jQuery 的类型,仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:
jQuery('#foo');
第三方声明文件
可以使用 @types
统一管理第三方库的声明文件。
@types
的使用方式很简单,直接用 npm
安装对应的声明模块即可,以 jQuery 举例:
npm install @types/jquery --save-dev
可以通过:TypeSearch 搜索需要的第三方声明文件
全局变量
使用全局变量的声明文件时,如果是以 npm install @types/xxx --save-dev
安装的,则不需要任何配置。如果是将声明文件直接存放于当前项目中,则建议和其他源码一起放到 src 目录下(或者对应的源码目录下):
/path/to/project
├── src
| ├── index.ts
| └── my_file.d.ts
└── tsconfig.json
全局变量的声明文件主要有以下几种语法:
declare var
声明全局变量declare function
声明全局方法declare class
声明全局类declare enum
声明全局枚举类型declare namespace
声明(含有子属性的)全局对象interface 和 type
声明全局类型
declare var
声明全局变量
// src/my_file.d.ts
// 使用 let 和 var 没有区别
// 当使用 const 定义时,表示此时的全局变量是一个常量
declare let myPara: (selector: string) => any;
// src/index.ts
myPara('#foo');
// 使用 declare let 定义的 myPara 类型,允许修改这个全局变量
myPara = function(selector) {
return document.querySelector(selector);
};
需要注意的是,声明语句中只能定义类型,切勿在声明语句中定义具体的实现
declare function
声明全局方法
// src/my_file.d.ts
declare function myFun: (selector: string) :any;
// 在函数类型的声明语句中,函数重载也是支持的
declare function myFun(domReadyCallback: () => any): any;
// src/index.ts
myFun('#foo');
myFun(function() {
alert('Dom Ready!');
});
declare class
声明全局类
// src/Animal.d.ts
declare class Animal {
name: string;
constructor(name: string);
sayHi(): string;
}
// src/index.ts
let cat = new Animal('Tom');
同样的,declare class
语句也只能用来定义类型,不能用来定义具体的实现
declare enum
声明全局枚举类型
// src/Directions.d.ts
declare enum Directions {
Up,
Down,
Left,
Right
}
// src/index.ts
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
interface 和 type
声明全局类型:除了全局变量之外,可能有一些类型我们也希望能暴露出来。在类型声明文件中,可以直接使用 interface
或 type
来声明一个全局的接口或类型(interface
前是不需要 declare
的)
// src/jQuery.d.ts
interface AjaxSettings {
method?: 'GET' | 'POST'
data?: any;
}
declare namespace jQuery {
function ajax(url: string, settings?: AjaxSettings): void;
}
// src/index.ts
let settings: AjaxSettings = {
method: 'POST',
data: {
name: 'foo'
}
};
jQuery.ajax('/api/post_something', settings);
npm 包
npm 包的声明文件主要有以下几种语法:
export
导出变量export namespace
导出(含有子属性的)对象export default
ES6 默认导出export =
commonjs 导出模块
tsconfig.json
内容
{
"compilerOptions": {
"module": "commonjs",
"baseUrl": "./",
"paths": {
"*": ["types/*"]
}
}
}
npm 包的声明文件与全局变量的声明文件有很大区别。在 npm 包的声明文件中,使用 declare
不再会声明一个全局变量,而只会在当前文件中声明一个局部变量。
只有在声明文件中使用 export
导出,然后在使用方 import
导入后,才会应用到这些类型声明。
export
导出变量
// types/foo/index.d.ts
export const name: string;
export function getName(): string;
export class Animal {
constructor(name: string);
sayHi(): string;
}
export enum Directions {
Up,
Down,
Left,
Right
}
export interface Options {
data: any;
}
对应的导入和使用模块应该是这样:
// src/index.ts
import { name, getName, Animal, Directions, Options } from 'foo';
console.log(name);
let myName = getName();
let cat = new Animal('Tom');
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
let options: Options = {
data: {
name: 'foo'
}
};
混用 declare
和 export
可以使用 declare
先声明多个变量,最后再用 export
一次性导出
// types/foo/index.d.ts
declare const name: string;
declare function getName(): string;
declare class Animal {
constructor(name: string);
sayHi(): string;
}
declare enum Directions {
Up,
Down,
Left,
Right
}
interface Options {
data: any;
}
export { name, getName, Animal, Directions, Options };
与全局变量的声明文件类似,interface
前是不需要 declare
的。
ES6 默认导出
在 ES6 模块系统中,使用 export default
可以导出一个默认值,使用方可以用 import foo from 'foo'
而不是 import { foo } from 'foo'
来导入这个默认值。
// types/foo/index.d.ts
export default function foo(): string;
// src/index.ts
import foo from 'foo';
foo();
注意,只有 function
、class
和 interface
可以直接默认导出,其他的变量需要先定义出来,再默认导出(例如下面的枚举类型)
// types/foo/index.d.ts
declare enum Directions {
Up,
Down,
Left,
Right
}
export default Directions;
commonjs 导出模块
// 整体导出
module.exports = foo;
// 单个导出
exports.bar = bar;
在 ts 中,针对这种模块导出,有多种方式可以导入,第一种方式是 const ... = require
:
// 整体导入
const foo = require('foo');
// 单个导入
const bar = require('foo').bar;
第二种方式是 import ... from
,注意针对整体导出,需要使用 import * as
来导入:
// 整体导入
import * as foo from 'foo';
// 单个导入
import { bar } from 'foo';
第三种方式是 import ... require
,这也是 ts 官方推荐的方式:
// 整体导入
import foo = require('foo');
// 单个导入
import bar = foo.bar;
三斜线指令
一个声明文件有时会依赖另一个声明文件中的类型
// types/moment-plugin/index.d.ts
import * as moment from 'moment';
declare module 'moment' {
export function foo(): moment.CalendarKey;
}
除了可以在声明文件中通过 import
导入另一个声明文件中的类型之外,还有一个语法也可以用来导入另一个声明文件,那就是三斜线指令。
与 namespace
类似,三斜线指令也是 ts 在早期版本中为了描述模块之间的依赖关系而创造的语法。随着 ES6 的广泛应用,现在已经不建议再使用 ts 中的三斜线指令来声明模块之间的依赖关系了。
// types/jquery-plugin/index.d.ts
/// <reference types="jquery" />
declare function foo(options: JQuery.AjaxSettings): string;
Reference
参考资料 声明文件